// 例:参数是一个对象，里面包含两个字段：firstName 和 lastName，也就是英文的名和姓，然后返回一个拼接后的完整名字
const getFullName = ({
  firstName,
  lastName,
}: { // 指定这个参数的类型，因为他是一个对象，所以这里来指定对象中每个字段的类型
  firstName: string; // 指定属性名为firstName和lastName的字段的属性值必须为string类型
  lastName: string;
}) => {
  return `${firstName} ${lastName}`;
};
getFullName({ firstName: 'fish', lastName: 'ss' })

// #接口编写
// 声明类型
interface Info {
  firstName: string;
  lastName: string;
};
const getFullNameInfo = ({ firstName, lastName }: Info) => `${firstName} ${lastName}`
console.log(getFullNameInfo); //({ firstName, lastName }) => `${firstName} ${lastName}`

// #可选属性
// 可选的
interface Vegetables {
  color?: string;
  type?: string;
}

const getVegetables = ({ color, type }: Vegetables) => {
  return ` ${color ? color + " " : ""}${type}`;
};

// #多余属性检查
getVegetables({
  type: "tomato",
  // size: "big" // 'size'不在类型'Vegetables'中
});

// #绕开多余属性检查
// 有时我们并不希望 TypeScript 这么严格地对我们的数据进行检查，比如我们只需要保证传入getVegetables的对象有type属性就可以了，至于实际使用的时候传入对象有没有多余的属性，多余属性的属性值是什么类型，这些都无所谓，那就需要绕开多余属性检查，有如下三个方法：

// @(1)使用类型断言
getVegetables({
  type: "tomato",
  size: "big"
} as Vegetables);

// @添加索引签名
interface Vegetables {
  color?: string;
  type?: string;
  [prop: string]: any
}
getVegetables({
  type: "tomato",
  size: "big" // 'size'不在类型'Vegetables'中
});

// @利用类型兼容性(不推荐)
interface Vegetables2 {
  type: string;
}
const getVegetables2 = ({ type }: Vegetables) => {
  return ` ${type}`;
};

const option = { type: "tomato", size: 12 };
getVegetables(option);

// #只读属性
//如果是定义一个常量，用const，如果这个值是作为对象的属性，用readonly
interface Role {
  readonly 0: string;
  readonly 1: string;
}
const role: Role = {
  0: 'superadmin',
  1: 'admin'
};
// role[1] = 'sss'  //无法为“1”赋值，因为它是只读属性。

// #函数类型
// 接口可以描述普通对象，还可以描述函数类型
// 我们管花括号里包着的内容为调用签名，它由带有参数类型的参数列表和返回值类型组成
interface AddFunc {
  (num1: number, num2: number): number;
}
const add: AddFunc = (n1, n2) => n1 + n2;
add(1, 2) //3

// #索引类型
// 使用接口描述索引的类型和通过索引得到的值的类型，比如一个数组[‘a’, ‘b’]，数字索引0对应的通过索引得到的值为’a’。
// 设置索引类型为 number。如果将属性名设置为字符串类型，则会报错；
// 但设置索引类型为字符串类型，那么即便你的属性名设置的是数值类型，也没问题。
// 因为 JS 在访问属性值的时候，如果属性名是数值类型，会先将数值类型转为字符串，然后再去访问
// 我们可以同时给索引和值都设置类型
interface RoleDic {
  [id: number]: string;
  // readonly [sid: string]: string; //只读
}
const role1: RoleDic = {
  0: "superadmin",
  1: "admin"
};
const role2: RoleDic = {
  // s: "superadmin",  // error 不能将类型"{ s: string; a: string; }"分配给类型"RoleDic"。
  // a: "admin"
};
// 定义了一个数组，索引为数值类型，值为字符串类型
const role3: RoleDic = ["super_admin", "admin"];

// #继承接口
// 和类一样,提高了接口的可复用性
// 例:定义一个Vegetables接口，它会对color属性进行限制。再定义两个接口，一个为Tomato，一个为Carrot，这两个类都需要对color进行限制，而各自又有各自独有的属性限制
// interface Vegetabless {
//   color: string;
// }
// interface Tomato {
//   color: string;
//   radius: number;
// }
// interface Carrot {
//   color: string;
//   length: number;
// }

// @color重复,使用继承来实现
interface Vegetabless {
  color: string
}
interface Food {
  type: string
}
interface Tomato extends Vegetabless, Food {
  radius: number;
}
interface Carrot extends Vegetabless {
  length: number;
}
const tomato: Tomato = {
  radius: 1.2,
  type: '蔬菜',
  color: 'white'
}
// const carrot: Carrot = {
//   length: 1, //类型 "{ length: number; }" 中缺少属性 "color"，但类型 "Carrot" 中需要该属性。
// }

// #混合类型接口
// 在 JS 中，函数是对象类型。对象可以有属性，所以有时我们的一个对象，它既是一个函数，也包含一些属性。比如我们要实现一个计数器函数，比较直接的做法是定义一个函数和一个全局变量
let count = 0;
const countUp = () => count++;

// TypeScript 支持直接给函数添加属性
// javascript
let countUp2 = () => {
  return ++countUp.count;
};
countUp.count = 0;
console.log(countUp2()); // 1
console.log(countUp2()); // 2

// 使用混合接口
interface Counter {
  (): void; // 这里定义Counter这个结构必须包含一个函数，函数的要求是无参数，返回值为void，即无返回值
  count: number; // 而且这个结构还必须包含一个名为count、值的类型为number类型的属性
}
const getCounter = (): Counter => { // 这里定义一个函数用来返回这个计数器
  const c = () => { // 定义一个函数，逻辑和前面例子的一样
    c.count++;
  };
  c.count = 0; // 再给这个函数添加一个count属性初始值为0
  return c; // 最后返回这个函数对象
};
const counter: Counter = getCounter(); // 通过getCounter函数得到这个计数器
counter();
console.log(counter.count); // 1
counter();
console.log(counter.count); // 2
